﻿using EnvDTE;
using EnvDTE80;
using Microsoft.VisualStudio.CommandBars;
using System;
using System.Reflection;
using System.Collections.Generic;
using Microsoft.VisualBasic.CompilerServices;

namespace MoXAML.Infrastructure
{
    public abstract class CommandBase : ICommandBase
    {

        private string _commandName;
        private string _caption;
        private bool _disposed;
        private CommandBarControl _control;

        public CommandBase()
        {
            ParentCommandBar = new List<CommandBarType>();
        }

        public void Initialize(DTE2 application, AddIn addInInstance)
        {
            this.AddInInstance = addInInstance;
            this.Application = application;
        }

        public string CommandName
        {
            get
            {
                return _commandName;
            }
            set
            {
                _commandName = value;
            }
        }

        public string Caption
        {
            get
            {
                return _caption;
            }
            set
            {
                _caption = value;
            }
        }

        public List<CommandBarType> ParentCommandBar
        {
            get;
            protected set;
        }

        /// <summary>
        /// Register the command.
        /// </summary>
        /// <param name="parentControl"></param>
        public void Register(CommandBarPopup parentControl)
        {
            try
            {
                Command command = BuildNamedCommand(CommandName);
                _control = (CommandBarControl)command.AddControl(parentControl.CommandBar, parentControl.CommandBar.Controls.Count + 1);
                _control.Caption = Caption;
            }
            catch (Exception ex)
            {
                ProjectData.SetProjectError(ex);
                ProjectData.ClearProjectError();
            }
        }

        /// <summary>
        /// Disconnect the command.
        /// </summary>
        public virtual void Close()
        {
            if (_control == null) return;
            _control.Delete(Missing.Value);
            _control = null;
        }

        /// <summary>
        /// Get the status that the command is at.
        /// </summary>
        /// <returns>The command status.</returns>
        /// <remarks>By default, the command status we are going with is vsCommandStatus.vsCommandStatusEnabled | vsCommandStatus.vsCommandStatusSupported.</remarks>
        public virtual vsCommandStatus GetStatus()
        {
            return vsCommandStatus.vsCommandStatusEnabled | vsCommandStatus.vsCommandStatusSupported;
        }

        /// <summary>
        /// Can this command be executed?
        /// </summary>
        /// <param name="executeOption">The execution options.</param>
        /// <returns>Returns true if the command can be executed, false otherwise.</returns>
        public virtual bool CanExecute(vsCommandExecOption executeOption)
        {
            return executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault;
        }

        public virtual void Execute() {}

        public void Dispose()
        {
            OnDispose(true);
        }

        protected CodeElement GetCodeElementAtTextPoint()
        {
            return GetCodeElementAtTextPoint(vsCMElement.vsCMElementClass);
        }

        protected CodeElement GetCodeElementAtTextPoint(vsCMElement elementType)
        {
            TextPoint pt = GetCursorTextPoint();
            if (pt == null) return null;

            return GetCodeElementAtTextPoint(elementType, Application.ActiveDocument.ProjectItem.FileCodeModel.CodeElements, pt);
        }

        protected CodeElement GetCodeElementAtTextPoint(vsCMElement requestedElementKind, CodeElements elements, TextPoint pt)
        {
            CodeElement el = null;

            if (pt != null)
            {
                if (elements != null)
                {
                    foreach (CodeElement element in elements)
                    {
                        if (!element.StartPoint.GreaterThan(pt) && !element.EndPoint.LessThan(pt))
                        {
                            if (requestedElementKind == element.Kind)
                            {
                                return element;
                            }

                            CodeElements childElements = GetCodeElementMembers(element);
                            if (childElements != null)
                            {
                                el = GetCodeElementAtTextPoint(requestedElementKind, childElements, pt);
                                if (el != null)
                                {
                                    return el;
                                }
                            }
                        }
                    }
                }
            }

            return el;
        }

        private CodeElements GetCodeElementMembers(CodeElement element)
        {
            if (element is CodeNamespace)
                return ((CodeNamespace)element).Members;
            if (element is CodeType)
                return ((CodeType)element).Members;
            if (element is CodeFunction)
                return ((CodeFunction)element).Parameters;
            return null;
        }

        protected TextPoint GetCursorTextPoint()
        {
            TextDocument doc = null;
            TextPoint pt = null;
            try
            {
                doc = (TextDocument)Application.ActiveDocument.Object(string.Empty);
                pt = doc.Selection.ActivePoint;
            }
            catch (Exception ex)
            {
                ProjectData.SetProjectError(ex);
                ProjectData.ClearProjectError();
            }
            return pt;
        }

        /// <summary>
        /// Save the current document.
        /// </summary>
        protected void SaveCurrentDocument()
        {
            Application.ActiveDocument.Save();
        }

        /// <summary>
        /// Save all documents.
        /// </summary>
        protected void SaveAllDocuments()
        {
            Application.Documents.SaveAll();
        }

        protected AddIn AddInInstance { get; set; }

        protected DTE2 Application { get; set; }

        protected virtual void OnDispose(bool dispose)
        {
            if (!_disposed && dispose)
            {
                Close();
                _disposed = true;
                GC.SuppressFinalize(this);
            }
        }

        /// <summary>
        /// Get the <see cref="Project"/>.
        /// </summary>
        protected Project Project
        {
            get
            {
                if (Application == null || Application.SelectedItems == null)
                    return null;
                return Application.SelectedItems.Item(1).ProjectItem.ContainingProject as Project;
            }
        }
        #region Command build methods
        private Command BuildCommand(string commandText)
        {
            Command command = null;
            // This line seems to be the problem.
            try
            {
                command = Application.Commands.Item(GetFullCommandName(commandText), -1);

            }
            catch (Exception ex)
            {
                ProjectData.SetProjectError(ex);
                ProjectData.ClearProjectError();
            } 
            return command;
        }

        private object GetFullCommandName(string commandText)
        {
            return AddInInstance.ProgID + "." + commandText;
        }

        private Command BuildNamedCommand(string commandText)
        {
            Command command = BuildCommand(commandText);
            if (command == null)
            {
                object[] array = null;
                command = Application.Commands.AddNamedCommand(AddInInstance, commandText, string.Empty, string.Empty, false, 0, ref array, 3);
            }
            return command;
        }
        #endregion

    }
}